#include <arch/cpu/soc/s3c2440.h>
#include <arch/arm.h>
#include <arch/io.h>
#include <config.h>
#include <clk.h>
#include <errno.h>

enum plls {
	MPLL = 0,
	UPLL = 1
};

#if (CONFIG_CLKIN_FREQ == 12000000)
#  if (CONFIG_FCLK_FREQ == 304000000)
#    define MPLL_MDIV 0x44
#    define MPLL_PDIV 1
#    define MPLL_SDIV 1
#  elif (CONFIG_FCLK_FREQ == 405000000)
#    define MPLL_MDIV 0x7f
#    define MPLL_PDIV 2
#    define MPLL_SDIV 1
#  endif
#else 
#endif

#ifndef __TOP_HALF__

static uint32_t get_pllclk(unsigned int pll)
{
    struct s3c2440_clock_power *clk_power = s3c2440_get_base_clock_power();
    uint32_t r, m, p, s;

    if (pll == MPLL)
        r = readl(&clk_power->mpllcon);
    else 
        r = readl(&clk_power->upllcon);

    m = ((r & 0xFF000) >> 12) + 8;
    p = ((r & 0x003F0) >> 4) + 2;
    s = r & 0x3;

    if (pll == MPLL)
        return 2 * m * (CONFIG_CLKIN_FREQ / (p << s));

    return (CONFIG_CLKIN_FREQ * m) / (p << s);
}

#else

static inline uint32_t get_pllclk(unsigned int pll)
{
	if (pll == MPLL)
		return CONFIG_FCLK_FREQ;
	else 
		return 0;
}

#endif

static inline uint32_t get_fclk(void)
{
	return get_pllclk(MPLL);
}

static inline uint32_t get_uclk(void)
{
	return get_pllclk(UPLL);
}


#ifndef __TOP_HALF__ 

static uint32_t get_hclk(void)
{
    struct s3c2440_clock_power *clk_power = s3c2440_get_base_clock_power();

    switch (readl(&clk_power->clkdivn) & 0x6) {
    default:
    case 0:
        return get_fclk();
    case 2:
        return get_fclk() / 2;
    case 4:
        return (readl(&clk_power->camdivn) & (1 << 9)) ?
            get_fclk() / 8 : get_fclk() / 4;
    case 6:
        return (readl(&clk_power->camdivn) & (1 << 8)) ?
            get_fclk() / 6 : get_fclk() / 3;
    }
}

#else

static inline uint32_t get_hclk(void)
{
	return get_fclk() / CONFIG_FCLK_HCLK_RATIO;
}

#endif 

#ifndef __TOP_HALF__

static uint32_t get_pclk(void)
{
    struct s3c2440_clock_power *clk_power = s3c2440_get_base_clock_power();

    return (readl(&clk_power->clkdivn) & 1) ? get_hclk() / 2 : get_hclk();
}

#else

static inline uint32_t get_pclk(void)
{
	return get_hclk() / CONFIG_HCLK_PCLK_RATIO;
}

#endif

#ifdef __TOP_HALF__ 

static int clock_get_ctrlbit(unsigned int id)
{
	switch (id) {
		case CLK_NAND:
			return 4;
		case CLK_UART0:
			return 10;
		case CLK_UART1:
			return 11;
		case CLK_UART2:
			return 12;
		case CLK_PWMTIMER:
			return 8;
		case CLK_GPIO:
			return 13;
		default:
			return -1;
	}
}

struct clk *clk_get_by_id(unsigned int id)
{
	return (struct clk *)id;
}

struct clk *clk_get(const char *name)
{
	return ERR_PTR(-ENOTSUPP);
}

int clk_enable(struct clk *clk)
{
	struct s3c2440_clock_power *clk_power = s3c2440_get_base_clock_power();
	int ctrlbit = clock_get_ctrlbit((unsigned int)clk);

	if (ctrlbit < 0)
		return -EINVAL;

	writel(readl(&clk_power->clkcon) | (1 << ctrlbit), &clk_power->clkcon);
	return 0;
}

int clk_disable(struct clk *clk)
{
	struct s3c2440_clock_power *clk_power = s3c2440_get_base_clock_power();
	int ctrlbit = clock_get_ctrlbit((unsigned int)clk);

	if (ctrlbit < 0)
		return -EINVAL;

	writel(readl(&clk_power->clkcon) & ~(1 << ctrlbit), &clk_power->clkcon);
	return 0;
}

uint32_t clk_get_rate(struct clk *clk)
{
	unsigned int id = (unsigned int)clk;

	switch (id) {
		case CLK_FCLK:
			return get_fclk();
		case CLK_UCLK:
			return get_uclk();
		case CLK_HCLK:
			return get_hclk();
		case CLK_PCLK:
			return get_pclk();
	}
	return 0;
}

#endif 

void s3c2440_clocks_init(void)
{
	struct s3c2440_clock_power *clk_power = s3c2440_get_base_clock_power();
	uint32_t tmp32;
	struct clk *clk_nand;

	writel(0xffffffff, &clk_power->locktime);	// U_LTIME = M_LTIME = 300us

	writel((MPLL_MDIV << 12) | (MPLL_PDIV << 4) | (MPLL_SDIV << 0), &clk_power->mpllcon);
	
	tmp32  = readl(&clk_power->clkdivn);
	tmp32 &= ~7;
#if (CONFIG_FCLK_HCLK_RATIO == 1) 
	tmp32 |= (0 << 1);
#elif (CONFIG_FCLK_HCLK_RATIO == 2)
	tmp32 |= (1 << 1);
#elif (CONFIG_FCLK_HCLK_RATIO == 4) || (CONFIG_FCLK_HCLK_RATIO == 8)
	tmp32 |= (2 << 1);
#elif (CONFIG_FCLK_HCLK_RATIO == 3) || (CONFIG_FCLK_HCLK_RATIO == 6)
	tmp32 |= (3 << 1);
#else
#error "CONFIG_FCLK_HCLK_RATIO is error!"
#endif
#if (CONFIG_HCLK_PCLK_RATIO == 1)
	tmp32 |= (0 << 0);
#elif (CONFIG_HCLK_PCLK_RATIO == 2)
	tmp32 |= (1 << 0);
#else
#error "CONFIG_HCLK_PCLK_RATIO is error!"
#endif
	writel(tmp32, &clk_power->clkdivn);

	tmp32  = readl(&clk_power->camdivn);
	tmp32 &= ~(3 << 8);
#if (CONFIG_FCLK_HCLK_RATIO == 6)
	tmp32 |= (1 << 8);
#elif (CONFIG_FCLK_HCLK_RATIO == 8)
	tmp32 |= (1 << 9);
#endif
	writel(tmp32, &clk_power->camdivn);

	writel((1 << 4), &clk_power->clkcon); 

	// enable the clock of NAND and disable all other clocks, because of the 4KB SRAM
	clk_nand = clk_get_by_id(CLK_NAND);
	if (IS_ERR(clk_enable(clk_nand))) {
		while (1);
	}

#if (CONFIG_FCLK_HCLK_RATIO == 1) 
	/* fast bus mode */
	set_cr(get_cr() & ~(CR_NF | CR_IA));	
#else 
	/* asynchronous bus mode */
	set_cr(get_cr() | CR_NF | CR_IA);
#endif
}

